﻿import RealisticBooks.TextUtil;
import RealisticBooks.BookSection;
import RealisticBooks.BookLine;
import flash.external.ExternalInterface;
import RealisticBooks.BookPage;
import RealisticBooks.BookProperties;
import RealisticBooks.BookLink;
import RealisticBooks.BookImage;

class RealisticBooks.PaginateText {
/**
 * The book properties
 * @type BookProperties
 */
private var bookProp:BookProperties;
/**
 * The page width
 * @type Number
 */
private var pageW:Number;
/**
 * The page height
 * @type Number
 */
private var pageH:Number;
/**
 * The list of pages from the text
 * @type Array
 */
private var pages:Array;

/**
 * Constructor
 * Parameters:
 * @param {BookProperties} bProp the properties of the realistic book
 */
public function PaginateText (bProp:BookProperties) {
	this.bookProp = bProp;
	pageW = bookProp.stageW - bookProp.left_margin - bookProp.right_margin - bookProp.xAdd;
	pageH = bookProp.stageH - bookProp.top_margin - bookProp.bottom_margin;
	pageH -= (bookProp.header_margin + bookProp.number_margin + bookProp.yAdd);
	
	//pageY = 0;- (bookProp.stageH/2) + bookProp.top_margin + bookProp.header_margin;
	//pageX = 0;- (bookProp.stageW/2) + bookProp.left_margin;
}
/**
 * Add the paragraph tag to line without it
 * Parameters:
 * @param {BookLine} ll  the current line
 * @param {String} 	 str the complete line content
 */
private function addParagraphTag (ll:BookLine, str:String):Boolean {
	var block_start:Array = ["<p", "<li", "<br"];var addP = false;
	if (TextUtil.findTerm(block_start, str) == false) {addP = true;
		ll.lineStartStyle = "<p align='" + ll.lineAlign + "'>" + 
							ll.lineStartStyle;
		ll.lineEndStyle += '</p>';
	}
	if (ll.isLineBreak == true) {
		ll.plain_text += " ";
	} else {
		ll.plain_text += "\r";
	}
	return addP;
}
/**
 * Add the font tag to the line start style
 * Parameters:
 * @param {BookLine} ll    the line object
 * @param {String}   ptstr from line text or line start style
 * @param {Number}   peid  the index the paragraph tag is found
 * Returns:
 * @return {Boolean} whether a font tag is added
 */
private function addFontToStartStyle (ll:BookLine, ptstr:String, 
									  peid:Number, str):Boolean {
	if ((ptstr == "lineStartStyle") && (peid == (ll[ptstr].length - 1))) {
		//we need to check the font tag from the line text
		ptstr = "lineText";
		peid = -1;
	}
	
	var ftid:Number = ll[ptstr].indexOf("<font", peid + 1);
	if (ftid == (peid + 1)) {//there is a font tag already
		var fteid:Number = ll[ptstr].indexOf(">", ftid + 1);
		var fstr:String = ll[ptstr].substring(peid + 1, fteid);
		if (fstr.indexOf("size='") == -1) {
			fstr = TextUtil.replaceString(fstr, "<font", "<font size='" +
										  ll.fontSize + "'");
		}
		if (fstr.indexOf("face='") == -1) {
			fstr = TextUtil.replaceString(fstr, "<font", "<font face='" +
										  ll.fontFace + "'");
		}
		if (fstr.indexOf("color='") == -1) {
			fstr = TextUtil.replaceString(fstr, "<font", "<font color='" +
										  ll.fontColor + "'");
		}
		fstr += '>';
		if (ll.lineWidth == 0) {
			fstr += '&#160;'
		}
		ll[ptstr] = ll[ptstr].substring(0, peid + 1) + fstr + 
					ll[ptstr].substr(fteid + 1);
	} else {
		var fstag = "<font" + " size='" + ll.fontSize + "'" +
					" color='" + ll.fontColor + "'" + 
					" face='" + ll.fontFace + "'>";
		if (ll.lineWidth == 0) {
			fstag += '&#160;';
		}
		ll[ptstr] = ll[ptstr].substring(0, peid + 1) + fstag + 
					ll[ptstr].substr(peid + 1);
		return true;
	}
	
	return false;
}
/**
 * Add the font tag to line without it
 * Parameters:
 * @param {BookLine} ll the current line
 * @param {String} 	 str the complete line content
 */
private function addFontTag (ll:BookLine, str:String):Void {
	//add the font tag after the p or li tag
	var btag:Array = ["p","li"];
		
	for (var bi:Number = 0; bi < btag.length; bi++) {
		var ptid:Number = ll.lineStartStyle.indexOf("<" + btag[bi]);
		var ptstr:String = "lineStartStyle";
		if (ptid == -1) {
			ptid = ll.lineText.indexOf("<" + btag[bi]);
			ptstr = "lineText";
		}
						
		if (ptid != -1) {
			var peid:Number = ll[ptstr].indexOf(">", ptid + 2);
			var added:Boolean = addFontToStartStyle(ll, ptstr, peid,str);
				
			if (added == true) {
				var etag:String = "</" + btag[bi] + ">";
				var etid:Number = ll.lineEndStyle.lastIndexOf(etag);
				var etstr:String = "lineEndStyle";
				if (etid == -1) {
					etid = ll.lineText.lastIndexOf(etag);
					etstr = "lineText";
				}
				
				ll[etstr] = ll[etstr].substring(0, etid) + 
							"</font>" + ll[etstr].substr(etid);
			}
				
			break;
		}
	}
}
/**
 * Write the whole content of the text in all pages
 */
public function outputPages ():String {
	//bookProp.txtField.htmlText = pageW + ',' + pageH;
	//bookProp.txtField.setNewTextFormat(bookProp.txtFmt);
	
	var html_txt:String = "";
	html_txt += bookProp.writeMetadataToFile();
	for (var i:Number = 0; i < pages.length; i++) {
		var currPage:BookPage = pages[i];
		html_txt += currPage.writePageToFile();
		
		/*if (i == 2) {
			for (var j = 0; j < currPage.page_line.length; j++) {
				var ll = currPage.page_line[j];
				bookProp.txtField.htmlText += currPage.page_line[j].lineStartStyle + 
											  currPage.page_line[j].lineText +
											  currPage.page_line[j].lineEndStyle;
			}
		}*/
	}
	html_txt += "\n";
	ExternalInterface.call("saveHTMLText",html_txt);
	return html_txt;
}
/**
 * Enter the line with the specified starting index and
 * end index from the paragraph into the page
 * Parameters:
 * @param {Number} sIndex the start line index
 * @param {Number} eIndex the end line index
 * @param {Array}  pp  the paragraphs
 * @param {BookPage} currPage the current page
 */
private function writeLineToPage (sIndex:Number, eIndex:Number, 
								  pp:Array, currPage:BookPage):Void {
	for (var k:Number = sIndex; k <= eIndex; k++) {
		var ll:BookLine = pp[k];
		
		if ((ll.lineLeading == null) || (ll.lineLeading == 0))
			ll.lineLeading = 2;
		
		var str:String = ll.lineStartStyle + ll.lineText + ll.lineEndStyle;	
		if (ll.lineHeight > 0) { 
			//we need to ensure that the line break split has a paragraph tag
			//and a font size tag
			if (ll.lineImage == null) {
				addParagraphTag(ll,str);
				addFontTag(ll,str);
				str = ll.lineStartStyle + ll.lineText + ll.lineEndStyle;	
			}
			
			//we need to change the line height according to line leading
			if (currPage.page_fill_count > 1) {
				var ol:BookLine = currPage.page_line[currPage.page_fill_index];
				currPage.page_text_height += ol.lineLeading;
			}
			ll.lineY = currPage.page_text_height;
			if (ll.lineImage == null)
				ll.lineHeight -= ll.lineLeading;
			currPage.page_text_height += ll.lineHeight;
			if (currPage.page_fill_count == 0) {
				currPage.page_text_height += ll.lineLeading;
			}
			currPage.page_fill_count++;
			currPage.page_fill_index = currPage.page_line.length;
			
			ll.lineX = 2;
			if (ll.lineAlign == "center") {
				ll.lineX = (pageW - ll.lineWidth)/2;
			} else if (ll.lineAlign == "right") {
				ll.lineX = pageW - ll.lineWidth - 2;
			}
			ll.lineIndex = ll.plain_text.length;
			if (currPage.page_line.length > 0) {
				ll.lineIndex += currPage.page_line[currPage.page_line.length - 1].lineIndex;
			}
			
			for (var li:Number = 0; li < ll.lineLinks.length; li++) {
				var bl:BookLink = ll.lineLinks[li];
				bl.linkY = ll.lineY;
				bl.linkX += ll.lineX;
			}
		}
		currPage.page_line.push(ll);
	}
}
/**
 * Get the latest section in the page
 * Parameters:
 * @param {BookPage} bPage the page
 * Returns:
 * @return {BookSection} the last section in the page
 */
private function getLastSection (bPage:BookPage):BookSection {
	if (bPage.page_section.length != bPage.page_section_length) {
		bPage.page_section_length = bPage.page_section.length;
		bPage.page_last_section = bPage.page_section[bPage.page_section_length - 1];
	}
	return bPage.page_last_section;
}
/**
 * Add a new page into the list of pages
 * Returns:
 * @return {BookPage} the created new page
 */
private function addNewPage ():BookPage {
	var lPage:BookPage = pages[pages.length - 1];
	var nPage:BookPage = new BookPage();
	
	nPage.page_num = pages.length;
	
	if (lPage != null) {
		nPage.page_section = [getLastSection(lPage)];
	}
	pages.push(nPage);
	
	return nPage;
}
/**
 * Given a paragraph, find the index of the last line that fit
 * into the page
 * Parameters:
 * @param {Array}  pp 	  		the paragraph
 * @param {Number} sIndex	  	the starting index
 * @param {Number} page_height 	the current page
 * Returns:
 * @return {Array} [the index of the last line, the new height
 * 					to the orphan lines]
 */
private function getOrphanIndex (pp:Array, sIndex:Number, 
								 page_height:Number):Array {
	var orphan_index:Number = sIndex - 1;
	for (var j:Number = sIndex; j < pp.length; j++) {
		var ll:BookLine = pp[j];
			
		if ((page_height + ll.lineHeight + ll.lineLeading) > pageH) {
			return [orphan_index, page_height];
		}
			
		orphan_index++;
		page_height	+= (ll.lineHeight + ll.lineLeading);
	}
	
	return [orphan_index, page_height];
}
/**
 * Given a paragraph, find the index that obey the orphan/widow rules
 * Parameters:
 * @param {Array}  pp 			the paragraph
 * @param {Number} sIndex 		the starting index
 * @param {Number} orphan_index	the index of the last line in the
 *								paragraphs that fit into the page
 * Returns:
 * @return {Number} the index that obey the orphan/widow rules
 */
private function getBestOrphanIndex (pp:Array, sIndex:Number,
									 orphan_index:Number):Number {
	var par_length:Number = pp.length - sIndex;//total number of lines
	if (orphan_index == (pp.length - 1)) {
		//the whole paragrah can fit into the page
		return orphan_index;
	} else if ((bookProp.orphanLine == 0) ||
			   ((orphan_index + 1 - sIndex) < bookProp.orphanLine) ||
			   ((par_length - bookProp.widowLine) < bookProp.orphanLine)) {
		//the whole paragraph cannot fit into the page
		//or it violates the orphan/widow rules
		return -1;
	} else {
		//some of the paragraph can fit into the page
		if ((pp.length - orphan_index - 1) < bookProp.widowLine) {
			orphan_index = Math.max(sIndex + bookProp.orphanLine - 1, 
									pp.length - bookProp.widowLine);
		}
			
		return orphan_index;
	}
}
/**
 * Set the section information for the specified page
 * Parameters:
 * @param {BookSection} ll the book section
 * @param {BookPage} currPage the book page
 */
private function addSectionToPage (ll:BookSection, currPage:BookPage):Void {
	if (currPage.page_section.length == 0)
		currPage.page_section = new Array();
	currPage.page_section.push(ll);
	ll.section_page = currPage;
}
/**
 * Write the paragraph for section title to the page
 * Parameters:
 * @param {Array} pSec	the paragraph for the section
 * @param {Array} pTxt  the paragraph for the section text
 * @param {BookPage} currPage the current page
 * @param {Array} sec_info the info about the orphan index
 *                         and the new page height
 * Returns:
 * @return {BookPage} the newly created page
 */
private function writeSectionParagraphToPage (pSec:Array, pTxt:Array,
											  currPage:BookPage, 
											  sec_info:Array):BookPage {
	
	var ll:BookSection = pSec[0].lineSection;
	if (ll.section_slidesname.length > 0) {
		currPage.page_slidesname = ll.section_slidesname;
		currPage.totalframes = ll.section_slidesname.length;
		addSectionToPage(ll, currPage);
		return currPage;
	}
	
	var old_height:Number = currPage.page_text_height;
	if (sec_info == null)
		sec_info = getOrphanIndex(pSec, 0, old_height);
	var orphan_index:Number = sec_info[0];
	if (orphan_index < (pSec.length - 1)) {
		//if the whole paragraph not fit, we should write it in a new page
		//but if the page is already new, we apply the normal orphan/widow
		//rules
		if (old_height > 0) {
			currPage = addNewPage();
			addSectionToPage(ll, currPage);
			currPage = writeSectionParagraphToPage(pSec, pTxt, currPage, null);
		} else {
			addSectionToPage(ll, currPage);
			currPage = writeTextParagraphToPage(pSec, 0, currPage, orphan_index);
			if (pTxt != null) {
				currPage = writeTextParagraphToPage(pTxt, 0, currPage, -1);
			}
		}
	} else {
		//we need to check so that the section title at least has
		//the content of a paragraph accompanying it
		if (pTxt != null) {
			var new_height:Number = sec_info[1];
			var o_index:Number = getOrphanIndex(pTxt, 0, new_height)[0];
			var p_index:Number = getBestOrphanIndex(pTxt, 0, o_index);
			
			if ((p_index == -1) && (old_height > 0)) {
				currPage = addNewPage();
				o_index = -1;
			}
			addSectionToPage(ll, currPage);
			currPage = writeTextParagraphToPage(pSec, 0, currPage, orphan_index);
			currPage = writeTextParagraphToPage(pTxt, 0, currPage, o_index);
		} else {
			addSectionToPage(ll, currPage);
			currPage = writeTextParagraphToPage(pSec, 0, currPage, orphan_index);
		}
	}
	
	return currPage;
}
/**
 * Write the paragraph of text to the page, if it does not fit
 * write it to a new page according to the orphan/widow
 * rules
 * Parameters:
 * @param {Array}    pp 	  	  the paragraph
 * @param {Number}   sIndex   	  the starting index
 * @param {BookPage} currPage 	  the current page
 * @param {Number}	 orphan_index the orphan index
 * Returns:
 * @return {BookPage} the newly created page
 */
private function writeTextParagraphToPage (pp:Array, sIndex:Number,
							  		   	   currPage:BookPage, 
										   orphan_index:Number):BookPage {
	if (sIndex >= pp.length)
		return currPage;
	
	var ll:BookLine = pp[0];
	if (ll.lineSlidesname.length > 0) {
		currPage.page_slidesname = ll.lineSlidesname;
		currPage.totalframes = ll.lineSlidesname.length;
		return currPage;
	} else if (ll.lineNewpage == true) {
		return currPage;
	}
	
	var nsIndex:Number = 0;//the new index for the new page
	if (orphan_index < 0) {
		orphan_index = getOrphanIndex(pp, sIndex, 
									  currPage.page_text_height)[0];
	}
	if ((currPage.page_text_height == 0) && (orphan_index < sIndex)) {
		//need to have sanity check so that at least it will write
		//one line of the paragraph into the page when the page is new
		writeLineToPage (sIndex, sIndex, pp, currPage);
		nsIndex = sIndex + 1;
	} else {
		var best_orphan:Number = getBestOrphanIndex (pp, sIndex,
									 				 orphan_index);
		if (best_orphan == -1) {
			nsIndex = sIndex;
		} else {
			writeLineToPage(sIndex, best_orphan, pp, currPage);
			nsIndex = best_orphan + 1;
		}
	}
	
	if (nsIndex < pp.length) {
		currPage = addNewPage();
		currPage = writeTextParagraphToPage(pp, nsIndex, currPage, -1);
	}
	
	return currPage;
}
/**
 * Paginating the content of the page
 */
public function paginate (currPage:BookPage):Array {
	pages = [];
	var nPage:BookPage = new BookPage();
	nPage.page_num = pages.length;
	if (currPage.page_section.length > 0)
		nPage.page_section = [currPage.page_section[0]];
	pages.push(nPage);
	
	for (var i:Number = 0; i < currPage.page_paragraphs.length; i++) {
		var pp:Array = currPage.page_paragraphs[i];
		
		var ll:BookLine = pp[0];
		
		if (nPage.page_slidesname.length > 0) {
			nPage = addNewPage();
		}
		
		if (ll.lineSection != null) {
			if ((ll.lineSection.section_newpage == true) ||
				(ll.lineSection.section_slidesname.length > 0)) {
				if (nPage.page_text_height > 0) {
					nPage = addNewPage();
				}
				
				//we need to make sure that the section starts on the
				//right hand page
				if ((ll.lineSection.section_newpage == true) && 
					((pages.length % 2) == 1)) {
					nPage = addNewPage();
				}
			}
			
			var nt:Array = null;
			if (ll.lineSection.section_slidesname.length == 0) {
				nt = currPage.page_paragraphs[i+1];
				var nl:BookLine = nt[0];
				if ((nl != null) && ((nl.lineSection != null) ||
					(nl.lineSlidesname.length > 0))) {
					nt = null;
				}
			}
			nPage = writeSectionParagraphToPage(pp, nt, nPage, null);
		} else {
			if ((ll.lineNewpage == true) && (nPage.page_text_height > 0)) {
				nPage = addNewPage();
			}
			
			nPage = writeTextParagraphToPage (pp, 0, nPage, -1);
		}
	}
	
	//removing the automatically added two new pages
	pages.shift();
	return pages;
}
}